package org.mcflier.appuntamento.core.modules;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import javax.net.ssl.HttpsURLConnection;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.mcflier.appuntamento.core.Factory;
import org.mcflier.appuntamento.core.exception.ModulesException;
import org.mcflier.appuntamento.core.exception.PropertiesException;
import org.mcflier.appuntamento.core.logger.LoggerManager;
import org.mcflier.appuntamento.core.properties.PropertiesManager;

public class ModulesManager implements IModule{

	public static final String MODULES_URL = "modules_url";
	public static final String MODULES_BACKUP_FILE = "modules_backup_file";

	public static final String MODULES_ROOT = "app";
	public static final String MODULES_NAME = "name";
	public static final String MODULES_VERSION = "version";
	public static final String MODULES_LASTCHANGE = "lastChange";
	public static final String MODULES_MODULE = "modules";

	public static final String MODULE_TAG = "module";
	public static final String MODULE_NAME = "name";
	public static final String MODULE_PATH = "path";

	/*private static ModulesManager instance;

	static{
		instance = null;
	}

	public static ModulesManager getInstnce() throws PropertiesException{
		if(instance == null)
			instance = new ModulesManager();

		return instance;
	}*/

	private Logger logger;
	private Properties properties;
	private Timestamp lastCheck;

	public ModulesManager() throws PropertiesException, ModulesException{
		this.logger = ((LoggerManager)Factory.getInstance().getModuleInstance(Factory.MODULE_LOGGER)).getLogger(this.getClass().getName());
		this.properties = ((PropertiesManager)Factory.getInstance().getModuleInstance(Factory.MODULE_PROPERTIES)).getProperties();

		this.lastCheck = new Timestamp(0);
	}

	public void loadModules() throws ModulesException{
		try {
			ModuleNode root = this.parseFile(this.readModulesFile(this.properties.getProperty(MODULES_URL)));
			try {
				Timestamp lastChange = Timestamp.valueOf(root.getAttribute(MODULES_LASTCHANGE));
				if(lastChange.after(this.lastCheck)){
					this.lastCheck = new Timestamp(Calendar.getInstance().getTimeInMillis());
					this.sendModuleContent(root);
				}
			} catch (IllegalArgumentException e) {
				this.logger.warning("The last change date from modules file is incorrect or missing");
				this.lastCheck = new Timestamp(Calendar.getInstance().getTimeInMillis());
				this.sendModuleContent(root);
			}
		} catch (ModulesException e) {
			throw new ModulesException("Error loading the modules file\n"+e.getMessage(), e);
		}
	}

	private void sendModuleContent(ModuleNode root) throws ModulesException{
		this.logger.info("Sending module content to each module");
		Map<String, ModuleNode> modules = root.getNode(MODULES_MODULE).getNodes();
		Set<String> keys = modules.keySet();
		Factory factory = Factory.getInstance();
		for (String key : keys) {
			ModuleNode module = modules.get(key);
			String path = module.getAttribute(MODULE_PATH);
			if(path!=null){
				factory.getModuleInstance(path).receiveModuleContent(module);
			}
			else{
				this.logger.warning("Module without path information: "+module.getAttribute(MODULES_NAME));
			}
		}
	}
	
	private String readBackupModulesFile() throws ModulesException{
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream(this.properties.getProperty(MODULES_BACKUP_FILE))));
			String modulesContent = "";
			String inputLine;
			while ((inputLine = reader.readLine()) != null){
				modulesContent+=inputLine;
			}
			reader.close();
			
			return modulesContent;
		} catch (IOException e) {
			throw new ModulesException("Error reading the modules backup file. This file is missing or unaccesible", e);
		}
	}

	private String readModulesFile(String modulesFilePath) throws ModulesException{
		String modulesContent="";
		
		try {
			URL url = new URL(modulesFilePath);
			HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
			con.addRequestProperty("User-Agent", "Appuntamento; "+System.getProperty("os.name")+" "+System.getProperty("os.version")+")");

			BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
			String inputLine;
			while ((inputLine = reader.readLine()) != null){
				modulesContent+=(inputLine+"\n");
			}
			reader.close();

			File backupFile = new File(System.getProperty("user.dir")+System.getProperty("file.separator")+this.properties.getProperty(MODULES_BACKUP_FILE));
			if(backupFile.exists())
				backupFile.delete();
			
			PrintWriter writer = new PrintWriter(backupFile);
			writer.println(modulesContent);				
			writer.close();
			this.logger.info("Backup modules file updated");
		} catch (MalformedURLException e) {
			modulesContent = this.readBackupModulesFile();
		} catch (FileNotFoundException e) {
			modulesContent = this.readBackupModulesFile();
		} catch (IOException e) {
			modulesContent = this.readBackupModulesFile();
		}

		return modulesContent;
	}

	private ModuleNode parseFile(String fileContent) throws ModulesException{
		JSONParser parser = new JSONParser();
		try {
			JSONObject root = (JSONObject) parser.parse(fileContent);
			ModuleNode node = this.fillModule(root);

			return node;
		} catch (ParseException e) {
			throw new ModulesException("Error parsing the modules file", e);
		}
	}

	private ModuleNode fillModule(JSONObject parent){
		ModuleNode node = new ModuleNode(null);
		Object temp = null;

		if(parent.size() == 1){
			String name = String.valueOf(parent.keySet().toArray()[0]);
			Object child = parent.get(name);
			if(!(child instanceof JSONArray)){
				node.setName(name);
				parent = (JSONObject)child;
			}
		}

		Set keys = parent.keySet();
		for (Object key : keys) {
			temp = parent.get(String.valueOf(key));

			if(temp instanceof JSONObject){
				JSONObject object = (JSONObject) temp;
				ModuleNode nodeTemp = this.fillModule(object);
				nodeTemp.setName(String.valueOf(key));
				node.add(String.valueOf(key), nodeTemp);
			}
			else if(temp instanceof JSONArray){
				JSONArray array = (JSONArray)temp;
				int i = 0;
				for (Object object : array) {
					ModuleNode arrayItem = this.fillModule((JSONObject)object);
					arrayItem.setName(String.valueOf(key));
					node.setName(String.valueOf(key));
					node.add(arrayItem.getName()+"-"+i++, arrayItem);
				}
			}
			else
				node.add(String.valueOf(key), String.valueOf(temp));
		}

		return node;
	}

	@Override
	public void receiveModuleContent(ModuleNode moduleContent) {
		// TODO Auto-generated method stub

	}
}
